// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "CaeInitCert.h"
#include "CaeLogConfig.h"
#include "../libs/openssl/include/openssl/rsa.h"
#include "../libs/openssl/include/openssl/x509.h"
#include "../libs/openssl/include/openssl/x509v3.h"
#include "../libs/openssl/include/openssl/ec.h"
#include "../libs/openssl/include/openssl/obj_mac.h"
#include "../libs/openssl/include/openssl/pem.h"

using namespace std;

static const string CAE_CERT_DIR = "/data/cloudAppEngine";
static const string CA_KEY_PATH = "/data/cloudAppEngine/CA.key";
static const string CA_CRT_PATH = "/data/cloudAppEngine/CA.crt";
static const string CAE_CSR_PATH = "/data/cloudAppEngine/CAE_server.csr";
const int CA_KEY_BIT = 2048;
const int CAE_KEY_BIT = 2048;
const unsigned char *country = reinterpret_cast<const unsigned char *>("CN");
const unsigned char *organization = reinterpret_cast<const unsigned char *>("CAE");
const unsigned char *commonName = reinterpret_cast<const unsigned char *>("CAE");

bool CaeInitCert::GenCaCert()
{
    LOGI("Begin generate CA cert.");
    // 生成RSA密钥对
    RSA *rsa_key = RSA_new();
    BIGNUM *bne = BN_new();
    BN_set_word(bne, RSA_F4);
    int ret = RSA_generate_key_ex(rsa_key, CA_KEY_BIT, bne, nullptr);
    if (ret == 0) {
        LOGE("Failed to generate rsa key.");
        RSA_free(rsa_key);
        BN_free(bne);
        return false;
    }

    // RSA密钥落盘
    FILE* fp = fopen(CA_KEY_PATH.c_str(), "w");
    if (!fp) {
        LOGE("Failed to open file CA.key.");
        RSA_free(rsa_key);
        BN_free(bne);
        return false;
    }
    PEM_write_RSAPrivateKey(fp, rsa_key, nullptr, nullptr, 0, nullptr, nullptr);
    fclose(fp);

    // 创建X.509证书请求
    X509_REQ *req = X509_REQ_new();
    X509_NAME *name = X509_NAME_new();
    X509_REQ_set_version(req, 0L);
    X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, country, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, organization, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, commonName, -1, -1, 0);
    X509_REQ_set_subject_name(req, name);

    // 签名X.509证书请求
    EVP_PKEY *p_key = EVP_PKEY_new();
    EVP_PKEY_assign_RSA(p_key, rsa_key);
    X509_REQ_sign(req, p_key, EVP_sha256());
    X509_REQ_set_pubkey(req, p_key);

    // 生成X.509证书
    X509 *cert = X509_new();
    X509_set_version(cert, 2L);
    ASN1_INTEGER_set(X509_get_serialNumber(cert), 1L);
    X509_set_subject_name(cert, X509_REQ_get_subject_name(req));
    X509_set_issuer_name(cert, X509_REQ_get_subject_name(req));
    X509_set_pubkey(cert, X509_REQ_get_pubkey(req));
    X509_gmtime_adj(X509_get_notBefore(cert), 0);
    X509_gmtime_adj(X509_get_notAfter(cert), 31536000L);
    if (X509_sign(cert, p_key, EVP_sha256()) == 0) {
        LOGE("Failed to generate x509 cert.");
        RSA_free(rsa_key);
        BN_free(bne);
        X509_REQ_free(req);
        X509_NAME_free(name);
        X509_free(cert);
        return false;
    }

    // CA证书落盘
    fp = fopen(CA_CRT_PATH.c_str(), "w");
    if (!fp) {
        LOGE("Failed to open file CA.crt.");
        RSA_free(rsa_key);
        BN_free(bne);
        X509_REQ_free(req);
        X509_NAME_free(name);
        X509_free(cert);
        return false;
    }
    PEM_write_X509(fp, cert);
    fclose(fp);

    // 释放资源
    RSA_free(rsa_key);
    BN_free(bne);
    X509_REQ_free(req);
    X509_NAME_free(name);
    X509_free(cert);
    return true;
}

bool CaeInitCert::GenCaePriKey()
{
    LOGI("Begin generate CAE private key.");
    // 生成EC参数
    EC_KEY* ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
    if (EC_KEY_generate_key(ec_key) == 0) {
        LOGE("Failed to generate ec key pair");
        EC_KEY_free(ec_key);
        return false;
    }
    EVP_PKEY* evp_key = EVP_PKEY_new();
    int ret = EVP_PKEY_assign_EC_KEY(evp_key, ec_key);
    if (ret == 0) {
        LOGE("Set ec_key to evp_key failed: %s.", ERR_reason_error_string(ERR_get_error()));
        EC_KEY_free(ec_key);
        return false;
    }

    // CAE私钥落盘
    FILE* fp = fopen(CaeConfigManage::GetPrivKeyPath().c_str(), "w");
    if (!fp) {
        LOGE("Failed to open file CAE_server.key.");
        EC_KEY_free(ec_key);
        return false;
    }
    PEM_write_PrivateKey(fp, evp_key, nullptr, nullptr, 0, nullptr, nullptr);
    fclose(fp);

    // 释放资源
    EC_KEY_free(ec_key);
    return true;
}

bool CaeInitCert::GenCaeCsr()
{
    LOGI("Begin generate CAE csr.");
    // 生成RSA密钥对
    RSA *rsa_key = RSA_new();
    BIGNUM *bne = BN_new();
    BN_set_word(bne, RSA_F4);
    int ret = RSA_generate_key_ex(rsa_key, CAE_KEY_BIT, bne, nullptr);
    if (ret == 0) {
        LOGE("Failed to generate rsa key.");
        RSA_free(rsa_key);
        BN_free(bne);
        return false;
    }

    // 生成证书请求
    X509_REQ *req = X509_REQ_new();
    X509_NAME *name = X509_NAME_new();
    X509_REQ_set_version(req, 0L);

    // 设置证书请求消息
    X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, country, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, organization, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, commonName, -1, -1, 0);
    X509_REQ_set_subject_name(req, name);

    // 签名证书请求
    EVP_PKEY *p_key = EVP_PKEY_new();
    EVP_PKEY_assign_RSA(p_key, rsa_key);
    X509_REQ_set_pubkey(req, p_key);
    if (X509_REQ_sign(req, p_key, EVP_sha256()) == 0) {
        LOGE("Failed to generate x509 cert.");
        RSA_free(rsa_key);
        BN_free(bne);
        X509_REQ_free(req);
        X509_NAME_free(name);
        return false;
    }

    // CAE请求文件落盘
    FILE* fp = fopen(CAE_CSR_PATH.c_str(), "w");
    if (!fp) {
        LOGE("Failed to open file CAE_server.csr.");
        RSA_free(rsa_key);
        BN_free(bne);
        X509_REQ_free(req);
        X509_NAME_free(name);
        return false;
    }
    PEM_write_X509_REQ(fp, req);
    fclose(fp);

    RSA_free(rsa_key);
    BN_free(bne);
    X509_REQ_free(req);
    X509_NAME_free(name);
    return true;
}

bool CaeInitCert::GenCaeCert()
{
    LOGI("Begin generate CAE cert.");
    // 读取csr文件
    X509_REQ *req = X509_REQ_new();
    FILE* fp = fopen(CAE_CSR_PATH.c_str(), "r");
    if (!fp) {
        LOGE("Failed to open file CAE_server.csr.");
        X509_REQ_free(req);
        return false;
    }
    PEM_read_X509_REQ(fp, &req, nullptr, nullptr);
    fclose(fp);

    // 读取CA根证书和私钥
    X509 *root_cert = X509_new();
    fp = fopen(CA_CRT_PATH.c_str(), "r");
    if (!fp) {
        LOGE("Failed to open file CA.crt.");
        X509_REQ_free(req);
        X509_free(root_cert);
        return false;
    }
    PEM_read_X509(fp, &root_cert, nullptr, nullptr);
    fclose(fp);
    EVP_PKEY *root_key = EVP_PKEY_new();
    fp = fopen(CA_KEY_PATH.c_str(), "r");
    if (!fp) {
        LOGE("Failed to open file CA.key.");
        X509_REQ_free(req);
        X509_free(root_cert);
        EVP_PKEY_free(root_key);
        return false;
    }
    PEM_read_PrivateKey(fp, &root_key, nullptr, nullptr);
    fclose(fp);

    // 读取CAE私钥
    EVP_PKEY *key = EVP_PKEY_new();
    fp = fopen(CaeConfigManage::GetPrivKeyPath().c_str(), "r");
    if (!fp) {
        LOGE("Failed to open file CAE_server.key.");
        X509_REQ_free(req);
        X509_free(root_cert);
        EVP_PKEY_free(root_key);
        EVP_PKEY_free(key);
        return false;
    }
    PEM_read_PrivateKey(fp, &key, nullptr, nullptr);
    fclose(fp);

    // 创建X509证书请求
    X509_REQ_set_pubkey(req, key);
    X509_NAME *name = X509_NAME_new();
    X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, country, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, organization, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, commonName, -1, -1, 0);
    X509_REQ_set_subject_name(req, name);

    // 签名X.509证书请求
    X509_REQ_sign(req, key, EVP_sha256());

    // 创建X509证书，设置版本号，序列号，有效期，签发者
    X509 *cert = X509_new();
    X509_set_version(cert, 2L);
    ASN1_INTEGER_set(X509_get_serialNumber(cert), 1L);
    X509_set_subject_name(cert, X509_REQ_get_subject_name(req));
    X509_set_issuer_name(cert, X509_get_subject_name(root_cert));
    X509_set_pubkey(cert, X509_REQ_get_pubkey(req));
    X509_gmtime_adj(X509_get_notBefore(cert), 0);
    X509_gmtime_adj(X509_get_notAfter(cert), 31536000L);

    // 签名X509证书
    if (!X509_sign(cert, root_key, EVP_sha256())) {
        LOGE("Failed to sign CAE X509 cert.");
        X509_REQ_free(req);
        X509_free(root_cert);
        EVP_PKEY_free(root_key);
        EVP_PKEY_free(key);
        X509_free(cert);
        return false;
    }

    // CAE证书落盘
    fp = fopen(CaeConfigManage::GetCertPath().c_str(), "w");
    if (!fp) {
        LOGE("Failed to open file CAE_server.crt.");
        X509_REQ_free(req);
        X509_free(root_cert);
        EVP_PKEY_free(root_key);
        EVP_PKEY_free(key);
        X509_free(cert);
        return false;
    }
    PEM_write_X509(fp, cert);
    fclose(fp);

    // 释放资源
    X509_REQ_free(req);
    X509_free(root_cert);
    EVP_PKEY_free(root_key);
    EVP_PKEY_free(key);
    X509_free(cert);
    return true;
}

bool CaeInitCert::CaeFileIsExists(const char *filePath)
{
    int ret = access(filePath, F_OK);
    if (ret == 0) {
        LOGI("File or dir: %s exists.", filePath);
        return true;
    }
    LOGI("File or dir: %s not exists.", filePath);
    if (strcmp(filePath, CAE_CERT_DIR.c_str()) != 0) {
        return false;
    }
    int status = mkdir(filePath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
    if (status != 0) {
        LOGE("Mkdir %s dir failed.", CAE_CERT_DIR.c_str());
        return false;
    } else {
        LOGI("Mkdir %s dir success.", CAE_CERT_DIR.c_str());
        return true;
    }
}

CaeInitCert::CaeInitCert() = default;

CaeInitCert::~CaeInitCert() = default;

void CaeInitCert::InitCaeCert()
{
    if (access(CaeConfigManage::GetPrivKeyPath().c_str(), F_OK) != -1 && access(CaeConfigManage::GetCertPath().c_str(), F_OK) != -1) {
        LOGI("CAE key and cert already exists.");
        return;
    }
    if (!CaeInitCert::CaeFileIsExists(CAE_CERT_DIR.c_str())) {
        return;
    }
    if (CaeInitCert::GenCaCert() && CaeInitCert::GenCaePriKey() && CaeInitCert::GenCaeCsr() && CaeInitCert::GenCaeCert()) {
        LOGI("Init CAE key and cert success.");
    } else {
        LOGI("Init CAE key and cert failed.");
    }
    string filenames[] = {CA_KEY_PATH, CA_CRT_PATH, CAE_CSR_PATH};
    uint32_t num_files = sizeof(filenames) / sizeof(filenames[0]);
    for (uint32_t i = 0; i < num_files; i++) {
        if (access(filenames[i].c_str(), F_OK) == 0) {
            LOGI("File %s exist, now delete.", filenames[i].c_str());
            remove(filenames[i].c_str());
        } else {
            LOGI("File %s not exists.", filenames[i].c_str());
        }
    }
}